package marshal

import (
	"errors"
	"fmt"
	"gitee.com/kingzyt/common/bufferPool"
	"gitee.com/kingzyt/common/bytes"
	"gitee.com/kingzyt/common/log"
	"gitee.com/kingzyt/common/util"
)

var bufPoolPull, bufPoolPush = bufferPool.BufPoolInit("marshalBufPool")

type MarshalEntity interface {
	Marshal() (data []byte, err error)
	MarshalTo(data []byte) (n int, err error)
	Unmarshal(data []byte) error
	Size() (n int)
}

func UnmarshalErr(name string) error {
	return fmt.Errorf("data [%s] unmarshal error", name)
}
func MarshalErr(name string) error {
	return fmt.Errorf("data [%s] marshal error", name)
}

// marshaledBuf需要在func中处理掉，不能保持转到外部使用，Marshal执行完毕后就释放空间了，会给其他函数使用，造成冲突
func Marshal(module string, data MarshalEntity, typeStr string, proc func(marshaledBuf []byte)) (ok bool) {
	// marshal need do all work in callback, for buf pool, so no proc, no marshal
	if proc == nil {
		return true
	}

	procBuf := func(marshaledBuf []byte) {
		defer func() {
			if r := recover(); r != nil {
				log.Error(module, "type:%s, Marshal ok, proc fail, panic happen: %s", typeStr, r)
				util.PrintPanicTrace()
			}
		}()
		proc(marshaledBuf)
	}

	if data == nil {
		procBuf(nil)
		return true
	}

	defer func() {
		if r := recover(); r != nil {
			log.Error(module, "type:%s, Marshal panic happen: %s", typeStr, r)
			util.PrintPanicTrace()
		}
	}()

	buf := bufPoolPull(data.Size())
	defer bufPoolPush(buf)

	n, err := data.MarshalTo(buf.Value)
	if err == nil {
		procBuf(buf.Value[:n])
		return true
	}

	log.Warn(module, "type:%s, MarshalTo fail: %s", typeStr, err)

	// maybe pull buf is too small for the data, just try to use marshal to get right size buf
	dataBuf, err := data.Marshal()
	if err != nil {
		log.Error(module, "type:%s, Marshal fail: %s", typeStr, err)
		return false
	}

	procBuf(dataBuf)
	return true
}

func MarshalManualRecycle(module string, data MarshalEntity, typeStr string) (recycleBuf *bytes.Element, marshaledBuf []byte, ok bool) {
	if data == nil {
		return nil, nil, true
	}

	defer func() {
		if r := recover(); r != nil {
			log.Error(module, "type:%s, Marshal panic happen: %s", typeStr, r)
			util.PrintPanicTrace()
		}
	}()

	buf := bufPoolPull(data.Size())

	n, err := data.MarshalTo(buf.Value)
	if err == nil {
		return buf, buf.Value[:n], true
	}

	// if fail, recycle buf
	defer bufPoolPush(buf)

	log.Warn(module, "type:%s, MarshalTo fail: %s", typeStr, err)

	// maybe pull buf is too small for the data, just try to use marshal to get right size buf
	dataBuf, err := data.Marshal()
	if err != nil {
		log.Error(module, "type:%s, Marshal fail: %s", typeStr, err)
		return nil, nil, false
	}

	return nil, dataBuf, true
}
func RecycleMarshalBuf(recycleBuf *bytes.Element) {
	if recycleBuf == nil {
		return
	}
	bufPoolPush(recycleBuf)
}

func MarshalCopy(module string, data MarshalEntity, typeStr string) (marshaledBuf []byte, err error) {
	ok := Marshal(module, data, typeStr, func(buf []byte) {
		marshaledBuf = util.BytesClone(buf)
	})
	if !ok {
		err = errors.New(typeStr + " marshal fail")
	}
	return
}

// filledData就是填了数据的data本身(dataContainer)，其实这里可以不传入filledData，通过外部自行走闭包即可
func Unmarshal(module string, dataContainer MarshalEntity, marshaledData []byte, typeStr string, proc func(filledData MarshalEntity)) (ok bool) {
	defer func() {
		if r := recover(); r != nil {
			log.Error(module, "type:%s, Unmarshal panic happen: %s", typeStr, r)
			util.PrintPanicTrace()
		}
	}()

	err := dataContainer.Unmarshal(marshaledData)
	if err != nil {
		log.Error(module, "type:%s, UnMarshal fail: %s", typeStr, err)
		return false
	}

	if proc != nil {
		proc(dataContainer)
	}
	return true
}

func Clone(dataSrc MarshalEntity, newContainer MarshalEntity) (ok bool) {
	Marshal("Clone", dataSrc, "Clone", func(marshaledBuf []byte) {
		Unmarshal("Clone", newContainer, marshaledBuf, "Clone", func(filledData MarshalEntity) {
			ok = true
		})
	})
	return
}
